在 Day 26 我們介紹了最小生成樹 (MST) 的概念,並提到兩個經典演算法: Kruskal 與 Prim。今天要深入探討 Kruskal 演算法,它以邊為導向,利用貪心法 (Greedy Algorithm) 與並查集 (Union-Find),有效率地找出 MST
Kruskal 的核心思想是: 每次選擇當前最小的邊,只要不形成環,就把它加入生成樹,直到選出 $V-1$ 條邊。
這是一種貪心策略 (Greedy Strategy),因為它每一步都選擇「目前最小代價」的選項,並且最終能證明得到的結果是最優解。
這裡就需要 Union-Find (並查集):
如果一條邊 $(u, v)$ 的兩端節點已經在同一個集合,代表加入它會形成環,必須跳過。
class UnionFind:
def __init__(self, n):
self.parent = list(range(n)) # 初始化,每個節點的父節點指向自己
self.rank = [0] * n # 用 rank 優化,避免退化成鏈狀
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x]) # 路徑壓縮
return self.parent[x]
def union(self, x, y):
root_x, root_y = self.find(x), self.find(y)
if root_x == root_y:
return False # 在同一集合,不能合併(會成環)
if self.rank[root_x] < self.rank[root_y]:
self.parent[root_x] = root_y
elif self.rank[root_x] > self.rank[root_y]:
self.parent[root_y] = root_x
else:
self.parent[root_y] = root_x
self.rank[root_x] += 1
return True
def kruskal(V, edges):
"""
V: 節點數
edges: (u, v, w) 的邊清單
"""
uf = UnionFind(V)
mst = []
edges.sort(key=lambda x: x[2]) # 按照權重排序
for u, v, w in edges:
if uf.union(u, v): # 如果不成環,加入 MST
mst.append((u, v, w))
if len(mst) == V - 1:
break
return mst
# 測試
V = 4
edges = [
(0, 1, 10),
(0, 2, 6),
(0, 3, 5),
(1, 3, 15),
(2, 3, 4)
]
mst = kruskal(V, edges)
print("Kruskal MST:", mst)
# 輸出: [(2, 3, 4), (0, 3, 5), (0, 1, 10)]
Kruskal 適合 稀疏圖 (Sparse Graph),因為邊排序的開銷不大。
特性 | Kruskal | Prim |
---|---|---|
導向 | 以邊為導向,先排序邊 | 以點為導向,逐步擴展 |
複雜度 | $O(E \log E)$ | $O(E \log V)$ |
適合 | 稀疏圖 | 稠密圖 |
工具 | Union-Find | Heap |
Kruskal 演算法透過貪心策略與並查集,能快速構造出最小生成樹。它特別適合 邊數較少、稀疏圖 的情境,也是演算法課程與面試中必考的重點。